/*
 * File: Breakout.java
 * -------------------
 * Name: Diego A. Falciola
 * Section Leader: Sam King
 * 
 * Basic implementation for the Breakout game
 */

import acm.graphics.*;
import acm.program.*;
import acm.util.*;

import java.awt.*;
import java.awt.event.*;

public class Breakout extends GraphicsProgram {

	/** Width and height of application window in pixels */
	public static final int APPLICATION_WIDTH = 400;
	public static final int APPLICATION_HEIGHT = 600;

	/** Dimensions of game board (usually the same) */
	private static final int WIDTH = APPLICATION_WIDTH;
	private static final int HEIGHT = APPLICATION_HEIGHT;

	/** Dimensions of the paddle */
	private static final int PADDLE_WIDTH = 60;
	private static final int PADDLE_HEIGHT = 10;

	/** Offset of the paddle up from the bottom */
	private static final int PADDLE_Y_OFFSET = 30;

	/** Number of bricks per row */
	private static final int NBRICKS_PER_ROW = 10;

	/** Number of rows of bricks */
	private static final int NBRICK_ROWS = 10;

	/** Separation between bricks */
	private static final int BRICK_SEP = 4;

	/** Width of a brick */
	private static final int BRICK_WIDTH =
	  (WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW;

	/** Height of a brick */
	private static final int BRICK_HEIGHT = 8;

	/** Radius of the ball in pixels */
	private static final int BALL_RADIUS = 10;

	/** Offset of the top brick row from the top */
	private static final int BRICK_Y_OFFSET = 70;

	/** Number of turns */
	private static final int NTURNS = 3;

	/** Ball speed */ 
	private static final double BALL_SPEED = 8.0; 

	private static final int PAUSE = 30;

	private static final boolean LIFE_LOST = false;

	// User Messages
	private static final String CLICK_TO_START = "Click to start";
	private static final String GAME_COMPLETED_WIN = 
		"WooHoo!! no more bricks left! hhmm... but game is over :(";
	private static final String GAME_COMPLETED_LOSS = 
		"So sad :( No more lives left. GAME OVER!";
	
	// the paddle
	private GRect paddle;
	// the ball
	private GOval ball; 
	// the balls speed
	private double vx, vy;
	// bricks left in screen
	int bricksLeft = NBRICK_ROWS * NBRICKS_PER_ROW;
	// messages displayed to the user
	GLabel messageToUser = new GLabel("");
	
	/** Sets up the Breakout program. */
	public void init() {
		addMouseListeners();
	}

	/* Method: run() */
	/** Runs the Breakout program. */
	public void run() { 
		startGame();
	}
	
	/**
	 *  Core of the game. Initializes elements 
	 *  and lets the ball run
	 */
	private void startGame(){
		int turnsLeft = NTURNS;
		
		initializeBricks();
		
		waitForStartClick();
		
		initializePaddle();
		
		initializeBall();
		
		// continue playing
		// while we have bricks ...
		// ... and lives! 
		while(bricksLeft > 0 && turnsLeft > 0){ 
			// not possible means that a life is lost
			// by hitting bottom wall
			if (!moveBallIfPossible()){
				// substract a life
				turnsLeft--;
				// and continue if possible
				if (turnsLeft>0){
					startNewTurn();
				}
			}
			
			// if a brick is hit, revert VY
			checkForCollisionAndRevertMovement();
			
			// and end when there are no more bricks, YAY! 
			if (bricksLeft==0){
				endGame(GAME_COMPLETED_WIN);
				waitForClick();
				return;
			}
			
			pause(PAUSE);
		}
		
		// no more lives left :(
		endGame(GAME_COMPLETED_LOSS);
		waitForClick();
	}
	
	/**
	 *  Displays initial message to the user and waits for user click
	 */
	private void waitForStartClick(){
		drawCenteredText(CLICK_TO_START);
		waitForClick();
		hideCenteredText();
		
	}
		
	/**
	 *  Starts a new life with a new VX direction
	 */
	private void startNewTurn(){
		remove(ball);
		remove(paddle);
		
		// calculate new vx, randomly
		randomizeVx();
		
		waitForStartClick();
		hideCenteredText();
		
		initializeBall();
		initializePaddle();
	}
	
	/**
	 * Moves the ball
	 * @return true if the ball did not hit the bottom wall
	 */
	private boolean moveBallIfPossible(){
		double ballX = ball.getX();
		double ballY = ball.getY();
		
		// check for colission with left and right wall
		if (ballX <= 0 || ballX >= WIDTH-2*BALL_RADIUS) {
			vx *= -1;
		}
		
		// check for colission with top wall
		if (ballY <= 0){
			vy *= -1;
		}
		 
		// if bottom wall is hit, lose a life
		if(ballY >= HEIGHT-2*BALL_RADIUS){
			return LIFE_LOST;
		}
		
		// move the ball
		ball.move(vx, vy);
		
		return true;
	}
	
	/**
	 * Remove paddle and ball from screen
	 * and display message to user
	 * @param endReason message to the user
	 */
	private void endGame(String endReason){
		remove(ball);
		remove(paddle);
		drawCenteredText(endReason);
	}
	
	/**
	 *  Sets a random number to VX 
	 */
	private void randomizeVx(){
		RandomGenerator rgen = RandomGenerator.getInstance();
		vx = rgen.nextDouble(1.0, 3.0);
		if (rgen.nextBoolean(0.5)){
			vx = -vx;
		}
	}
	
	/**
	 * Checks for colission and revert VY movement
	 */
	private void checkForCollisionAndRevertMovement(){
		GPoint[] ballCorners = new GPoint[4];
		double ballX = ball.getX();
		double ballY = ball.getY();
		
		// setup four corners
		ballCorners[0] = new GPoint(ballX, ballY);
		ballCorners[1] = new GPoint(ballX + BALL_RADIUS, ballY);
		ballCorners[2] = new GPoint(ballX, ballY + BALL_RADIUS);
		ballCorners[3] = new GPoint(ballX + BALL_RADIUS, ballY + BALL_RADIUS);
		
		GObject collidingObject;
		
		// check all four corners for colissions
		for (int ballCornerIndex =0; ballCornerIndex<4; ballCornerIndex++){
			collidingObject = getElementAt(ballCorners[ballCornerIndex].getX(), ballCorners[ballCornerIndex].getY());
			
			if (collidingObject != null){
				if (collidingObject!=paddle){
					remove(collidingObject);
					bricksLeft--;
				}
				// revert VY direction
				vy*=-1;
				
				// when a colission is detected, stop
				break;
			}
		}
	}
	
	/**
	 *  Creates a new paddle and draws it in screen
	 */
	private void initializePaddle(){
		GPoint point = getPaddleInitialPoint();
		
		paddle = new GRect(point.getX(), point.getY(), PADDLE_WIDTH, PADDLE_HEIGHT);
		paddle.setFilled(true);
		add(paddle);
	}
	
	/**
	 *  Creates a new ball, sets its VX and VY and draws it in screen
	 */
	private void initializeBall(){
		
		int x = WIDTH / 2;
		int y = HEIGHT / 2;

		ball = new GOval(x, y, BALL_RADIUS, BALL_RADIUS);
		ball.setFillColor(Color.BLACK);
		ball.setFilled(true);
		add(ball);
		
		randomizeVx();
		
		vy = BALL_SPEED;
	}
	
	
	/**
	 *  Draw NBRICK_ROWS rows in screen
	 */
	private void initializeBricks(){
		for (int row = 0; row < NBRICK_ROWS; row++){
			drawLineOfBricks(row);
		}
	}
	
	/**
	 * Draw a line of bricks in screen
	 * @param row row number
	 * @param brickColors a list of possible brick colors
	 */
	private void drawLineOfBricks(int row){
		for (int brickNumber = 0; brickNumber<NBRICKS_PER_ROW; brickNumber++){
			GPoint brickInitialPoint = getBrickInitialPoint(brickNumber, row);
			
			GRect rect = new GRect(brickInitialPoint.getX(), brickInitialPoint.getY(), BRICK_WIDTH, BRICK_HEIGHT);
			rect.setFilled(true);
			
			Color color = null;
			
			// set color according to which row is being rendered
			if (row==0 || row == 1){
				color = Color.RED;
			}
			if (row==2 || row == 3){
				color = Color.ORANGE;
			}
			if (row==4 || row == 5){
				color = Color.YELLOW;
			}
			if (row==6 || row == 7){
				color = Color.GREEN;
			}
			if (row==8 || row == 9){
				color = Color.CYAN;
			}
			
			rect.setColor(color);
			add(rect);
		}
	}
	
	/**
	 * Calculate where a brick is displayed
	 * depending on its column and row
	 * 
	 * @param column column number
	 * @param row row number
	 * @return a GPoint object with brick X and Y position
	 */
	private GPoint getBrickInitialPoint(int column, int row){
		GPoint point = new GPoint();
		
		int x =  column * BRICK_WIDTH+ (column+1)*BRICK_SEP;
		int y =  row * BRICK_HEIGHT + row*BRICK_SEP + BRICK_Y_OFFSET;
		
		point.setLocation(x,y);
		
		return point;
	}
	
	/**
	 * Calculate X and Y position of the Paddle
	 * @return a GPoint object with paddle X and Y position
	 */
	private GPoint getPaddleInitialPoint(){
		int x = getWidth()/2 - PADDLE_WIDTH/2;
		int y = getHeight() - PADDLE_Y_OFFSET;
		
		return new GPoint(x,  y);
	}
	
	
	/**
	 *   Mouse move event
	 */
	public void mouseMoved(MouseEvent e) {
		
		if (paddle!=null){
			// x and y location of mouse 
			Point newPosition = e.getPoint();
			
			if (newPosition.x < ((WIDTH- PADDLE_WIDTH)+ PADDLE_WIDTH/2) ){
				double newPositionX = newPosition.x - PADDLE_WIDTH/2;
				
				//do not let the paddle go off the screen
				if (newPositionX<0) newPositionX = 0;
				
				paddle.setLocation(newPositionX,HEIGHT - (PADDLE_Y_OFFSET + PADDLE_HEIGHT));
			}
		}
		
	}
	
	/**
	 * Draw a GLabel centered on screen
	 * @param text GLabel text
	 */
	private void drawCenteredText(String text){
		messageToUser.setLabel(text);
		messageToUser.setColor(Color.RED);
		messageToUser.setLocation(getWidth()/2-(text.length()/2*5.5), getHeight()/2);
		add(messageToUser);
	}
	
	/**
	 *  Hides centered text
	 */
	private void hideCenteredText(){
		remove(messageToUser);
	}
}